package com.google.code.p.restrpc.client.registry;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang.StringUtils;

import com.google.code.p.restrpc.client.ServiceNotRegisteredException;
import com.google.code.p.restrpc.client.cache.ServiceMetadataRepository;
import com.google.code.p.restrpc.client.cache.ServiceMetadataRepositoryFactory;
import com.google.code.p.restrpc.client.cache.ServiceMetadataRepositoryProvider;
import com.google.code.p.restrpc.directory.RemoteServiceDefinitionPack;

/**
 * @author dz
 */
public class CachedRegistryClientFactory implements
                                        RegistryClientFactory,
                                        ServiceMetadataRepositoryProvider {
    
    private ConcurrentHashMap<String, RegistryClient> registryClients = new ConcurrentHashMap<String, RegistryClient>();
    
    private ReentrantLock lock = new ReentrantLock();
    
    private RegistryClient registryClient;
    
    private RegistryClientFactory registryClientFactory;
    
    private ServiceMetadataRepository serviceMetadataRepository;
    
    /**
     * @param factory
     */
    public CachedRegistryClientFactory(RegistryClientFactory factory) {
        this.registryClientFactory = factory;
        this.serviceMetadataRepository = ServiceMetadataRepositoryFactory.getInstance()
                                                                         .getServiceMetadataRepository(registryClientFactory.getRegistryClient());
    }
    
    /**
     * @param factory
     * @param heartbeat
     */
    public CachedRegistryClientFactory(RegistryClientFactory factory,
                                       int heartbeat) {
        this.registryClientFactory = factory;
        this.serviceMetadataRepository = ServiceMetadataRepositoryFactory.getInstance()
                                                                         .getServiceMetadataRepository(heartbeat,
                                                                                                       registryClientFactory.getRegistryClient());
    }
    
    /**
     * @param factory
     */
    public CachedRegistryClientFactory(RegistryClientFactory factory,
                                       ServiceMetadataRepository serviceMetadataRepository) {
        this.registryClientFactory = factory;
        this.serviceMetadataRepository = serviceMetadataRepository;
    }
    
    /**
     * 
     */
    public ServiceMetadataRepository getServiceMetadataRepository() {
        return serviceMetadataRepository;
    }
    
    /**
     * @return
     */
    public RegistryClient getRegistryClient() {
        if (registryClient != null) {
            return registryClient;
        }
        try {
            lock.lock();
            if (registryClient != null) {
                return registryClient;
            }
            registryClient = new RegistryClient() {
                
                private RegistryClient registryClient = registryClientFactory.getRegistryClient();
                
                public RemoteServiceDefinitionPack lookup(String serviceName,
                                                          long lastModifiedMillis) {
                    if (StringUtils.isEmpty(serviceName)) {
                        throw new IllegalArgumentException("serviceName can't be null or empty!");
                    }
                    
                    RemoteServiceDefinitionPack result = serviceMetadataRepository.getRemoteServiceDefinition(serviceName);
                    if ((result != null) && (-1l == lastModifiedMillis)) {
                        return result;
                    }
                    
                    result = registryClient.lookup(serviceName,
                                                   lastModifiedMillis);
                    if (result == null) {
                        throw new ServiceNotRegisteredException(serviceName);
                    }
                    serviceMetadataRepository.putRemoteServiceDefinition(serviceName,
                                                                         result);
                    return result;
                }
                
            };
            return registryClient;
        }
        finally {
            lock.unlock();
        }
    }
    
    /**
     * 
     */
    public RegistryClient getRegistryClient(final String directoryServiceUrl) {
        if (StringUtils.isEmpty(directoryServiceUrl)) {
            throw new IllegalArgumentException("The 'directoryServiceUrl' can't be null or empty!");
        }
        
        RegistryClient result = registryClients.get(directoryServiceUrl);
        if (result != null) {
            return result;
        }
        
        registryClients.putIfAbsent(directoryServiceUrl, new RegistryClient() {
            
            private RegistryClient registryClient = registryClientFactory.getRegistryClient(directoryServiceUrl);
            
            public RemoteServiceDefinitionPack lookup(String serviceName,
                                                      long lastModifiedMillis) {
                if (StringUtils.isEmpty(serviceName)) {
                    throw new IllegalArgumentException("serviceName can't be null or empty!");
                }
                
                RemoteServiceDefinitionPack result = serviceMetadataRepository.getRemoteServiceDefinition(serviceName);
                if (result != null) {
                    return result;
                }
                
                result = registryClient.lookup(serviceName, lastModifiedMillis);
                if (result == null) {
                    throw new ServiceNotRegisteredException(serviceName);
                }
                serviceMetadataRepository.putRemoteServiceDefinition(serviceName,
                                                                     result);
                return result;
            }
            
        });
        
        return registryClients.get(directoryServiceUrl);
    }
    
}
